
if (typeof Function.prototype.bind !== 'function')
    Function.prototype.bind = function (obj) {
        var f = this;
        return function () {
            return f.apply(obj, arguments);
        };
    };


if (typeof Object.create !== 'function')
    Object.create = function(proto) {
        function F() {};
        F.prototype = proto;
        return new F();
    };


$.create = function(proto, args) {
    return $.extend(Object.create(proto), args);
};


function Fenton(config) {
    Fenton.config = config;

    if (!/tzoffset/.test(document.cookie))
        document.cookie = 'tzoffset=' + (new Date).getTimezoneOffset();

    $.ajaxSetup({
        type: 'POST',
        dataType: 'json',
        traditional: true,
        url: Fenton._context,
        dataFilter: Fenton._ajax_filter_data,
        error: Fenton._on_remote_error,
        success: Fenton._on_remote_success,
        complete: Fenton._on_remote_complete
    });

    $.log = Fenton.get_logger(config.debug);
    Fenton.install_widgets();
    Fenton.callbacks.refresh_screen = Fenton.refresh_screen;
    $.log.debug('Initialised');
};




$.extend(Fenton, {

    msie6: $.browser.msie && $.browser.version < 7,

    QUEUE_ID: 'xmlhttprequest',
    KEY_BACKSPACE: 8,
    KEY_TAB: 9,
    KEY_RETURN: 13,
    KEY_ESCAPE: 27,

    NOP: function () {},

    _request: null,
    _form: document.forms[0],
    _context: document.forms[0].action,

    widgets: {},
    callbacks: {},

    array: function (l) {
        try {
            return Array.prototype.concat.call(l);
        }
        catch (e) {
            for (var i = 0, l = l.length, r = []; i < l; ++i)
                r[i] = l[i++];
            return r;
        }
    },


    uniqid: (function () {
        var n = 0;
        return function () {
            return 'uniqid-' + ++n;
        };
    })(),


    pad_string: function(s, len, ch, r) {
        r = Math.abs(len) == len;
        len = Math.abs(len);
        while(s.length < len) {
            if (r)
                s+= ch;
            else
                s = ch + s;
        }
        return s;
    },


    pad10: function (x) {
        return x < 10 ? '0'+x : x;
    },


    parse_time: function (value) {
        var m = value.match(/^(\d\d?)[:\.]?(\d\d) *([aApP][mM])?$/)
        if (m !== null && m[1] < 24 && m[2] < 60) {
            if (m[3] && m[3].toLowerCase() == 'pm' && m[1] < 12)
                m[1] = 12 + 1 * m[1]
            return this.pad_string(m[1], -2, '0') + ':' + m[2];
        }
        return '';
    },


    parse_minutes: function (value) {
        var m = value.match(/^((\d+)\s*h)?\s*((\d+)\s*m?)$/i);
        if (m !== null && m[1] < 24 && m[2] < 60) {
            return m[1] + 'h' + m[2] + 'm';
        }
        return '';
    },

    format_tz: function (offset) {
        h = Math.floor(Math.abs(offset / 60)),
        m = Math.floor(Math.abs(offset % 60)),
        h = h < 10 ? '0' + h : h,
        m = m < 10 ? '0' + m : m,
        s = offset < 0 ? '-' : '+';
        return s + h + ':' + m;
    },


    format_iso_date: function (d) {
        var pad10 = this.pad10;
        return [d.getFullYear(),
                pad10(d.getMonth()+1),
                pad10(d.getDate())].join('-');
    },


    format_iso_timestamp: function (d) {
        var pad10 = this.pad10;
        return [d.getFullYear(),
                pad10(d.getMonth()+1),
                pad10(d.getDate())].join('-')
               + 'T'
               + [pad10(d.getHours()),
                  pad10(d.getMinutes()),
                  pad10(d.getSeconds())].join(':');
    },


    from_iso_date: function (x) {
        var d, t,
            r = /^(\d\d\d\d)-(\d\d)-(\d\d)([T ](\d\d):(\d\d).*)?$/,
            m = x.match(r);

        if (m) {
            if (m[4])
                return new Date(m[1], m[2] - 1, m[3], m[5], m[6]);
            else
                return new Date(m[1], m[2] - 1, m[3]);
        }
        return null;
    },


    stamp: function (w) {
        var t = ' ' + ((new Date()-window.__started)),
            l = t.length;
        w = w || 10;
        for (var i=0; i<w-l;++i)
            t+=' ';
        return t;
    },


    _fakelog_install: function () {
        $('<div id=javascript-log><pre></pre></div>')
            .css({
                left: 0,
                margin: 0,
                color: '#ccc',
                width: '200px',
                height: '40px',
                position: 'fixed',
                bottom: 0,
                opacity: Fenton.msie6 ? undefined : 0.9,
                overflow: 'hidden',
                'background-color': '#222',
                'z-index': 9999999
            })
            .click(Fenton._fakelog_click)
            .bgiframe()
            .appendTo(document.body);
        },


    _fakelog_append: function () {
        if (!Fenton._fakelog_installed)
            Fenton._fakelog_install();
        Fenton._fakelog_installed = true;
        var msg = Array.prototype.join.call(arguments, ' ');
        $('#javascript-log pre').append('<span>'+msg+'</span><br>');
        window.clearTimeout(Fenton._fakelog_scroll_timeout);
        Fenton._fakelog_scroll_timeout = window.setTimeout(Fenton._fakelog_scroll);
    },


    _fakelog_click: function () {
        if (Fenton.fakelog_orig_dimensions) {
            $(this).css({
                overflow: 'hidden',
                width: '200px',
                height: '40px'
            });
            Fenton.fakelog_orig_dimensions = null;
        }
        else {
            Fenton.fakelog_orig_dimensions = [$(this).width(), $(this).height()]
            $(this).css({
                overflow: 'auto',
                height: $(window).height()/3*2,
                width: '100%'
            });
        }
        Fenton._fakelog_scroll();
    },

    _fakelog_scroll: function () {
        $('#javascript-log').scrollTop($('#javascript-log pre').height());
    },


    get_logger: function(debug) {

        if (debug && window.console)
            return window.console;

        var nop = Fenton.NOP,
            write = debug ? Fenton._fakelog_append : nop;

        return {
            log: write,
            debug: write,
            info: write,
            warn: write,
            error: write,
            assert: write,
            dir: nop,
            trace: nop,
            group: nop,
            groupEnd: nop,
            time: nop,
            timeEnd: nop,
            profile: nop,
            profileEnd: nop,
            count: nop
        };
    },


    _disable_all: function (visual) {
        Fenton._requests_disabled = true;
        $('a[href]').removeAttr('href');
        $('button, input').attr('disabled', 'disabled');
        var style = {
            zIndex: 99998,
            position: 'absolute',
            top: 0,
            left: 0,
            right: 0,
            bottom: 0,
            opacity: 0 // '0.1'
        },
        $d = $('<div></div>')
            .css(style)
            .click(function () {return false})
            .appendTo(document.body);

        if (visual) {
            $('a, button').css('color', '#333');
            $d.css('background-color', '#d4d4d4');
            $d.fadeTo(10000, '0.9');
        }
    },


    _send_request: function (args) {
        Fenton._request = args;
        $.ajax(args);
    },

    _enqueue_request: function (args) {
        $(document).queue(Fenton.QUEUE_ID, function () {
            Fenton._send_request(args);
        });
    },


    _dequeue_request: function () {
        if (!Fenton._request)
            $(document).dequeue(Fenton.QUEUE_ID);
    },


    _retry_request: function () {
        $.log.debug('RETRY()');
        setTimeout(function() {
            Fenton._send_request(Fenton._request);
        }, 2000);
    },


    refresh_screen: function (url) {
        url = url || Fenton._context;
        $.log.debug('redirect: '+url);
        if (window.location.pathname == Fenton._context)
            window.location.replace(url);
        else
            window.location = url;
    },


    show_message: function (type, msg) {
        return $.notify[type](msg);
    },


    show_error: function (msg) {
        return $.notify.error(msg);
    },


    define_widgets: function (arg) {
        for (var i=0; i<arg.length; ++i)
            Fenton.define_widget(arg[i]);
    },

    define_widget: function (args) {
        var proto = args.extend ? Fenton.widgets[args.extend]
                                : Fenton.widgets.base_widget;
        return Fenton.widgets[args.widget_class] = $.create(proto, args);
    },


    install_widgets: function () {
        var ww = Fenton.config.widgets;
        for (var i=0; i<ww.length; ++i) {
            var w = ww[i],
                widget_id = w[0],
                widget_class = w[1],
                args = w[2] || {},
                proto = Fenton.widgets[widget_class];
            args.id = widget_id;
            ww[i] = proto ? $.create(proto, args).init() : null;
        }

        for (var i=0; i<ww.length; ++i)
            ww[i] && ww[i].bind && ww[i].bind();

        Fenton.config.widgets = null;
    },


    _ajax_filter_data: function (data, type) {
        return data ? data : {};
    },


    _retry_count: 0,
    _retry_max: 5,

    _on_remote_error: function (rq, status, ex) {

        var url = rq.getResponseHeader('x-debug-url');
        if (url && confirm('Server error.  Debug?'))
            return Fenton.refresh_screen(url);

        //Fenton._disable_all();
        $.log.error('error, status=' + status + ', rq.status=' + rq.status + ', ex=' + ex);
        $.log.trace();

        if ((status == 'noserver'
            || rq.status == '404'
            || rq.status == '502'
            || rq.status == '503')
            && ++Fenton._retry_count < Fenton._retry_max)
            return Fenton._retry_request();

        var msg = ex ? ex :
                    Fenton._retry_count ? 'Too many remote errors' :
                    rq.status >= 500 && rq.status < 600 ? 'Server error' :
                    '(status ' + status + ', ' + rq.status +')';

        Fenton._request = null;
        Fenton._retry_count = 0;
        Fenton.show_error(Fenton.config.error_message + ' ' + msg);
        Fenton._log_remote(msg);
    },

    _on_remote_success: function (data, status, rq) {

        if (rq.status == 0)
            return this.error.call(this, rq, 'noserver', null);

        Fenton._request = null;
        Fenton._retry_count = 0;

        if (Fenton.screen)
            Fenton.screen.heartbeat_pulse();

        if (this.target && this.target.ack_response)
            this.target.ack_response.call(this.target, this);

        var self = this,
            config = $.extend(Fenton.config, data),
            has_error = false,
            url = rq.getResponseHeader('x-location');

        if (url || self.refresh)
            return Fenton.refresh_screen(url);

        if (config.widgets)
            Fenton.install_widgets();

        if (config.form_pushback)
            $.each(config.form_pushback, function (k, v) {
                Fenton.model_form.set_value(k, v);
            });

        config.form_pushback = null;

        if (config.form_errors)
            $.each(config.form_errors, function (k, e) {
                has_error = has_error || !!e;
                Fenton.model_form.set_error(k, e);
            });

        if (has_error) {
            if (!config.form_error)
                config.form_error = Fenton.show_error(config.form_error_msg)
        }
        else {
            if (config.form_error)
                config.form_error.close();
            config.form_error = null;
            $(Fenton.screen).trigger('clean');
        }

        if (config.run_callbacks) {
            for (var i=0; i<config.run_callbacks.length; ++i)
                Fenton.callbacks[config.run_callbacks[i]].call(self);
        }

        if (config.callback_expr)
            eval(config.callback_expr);
        config.run_callbacks = null;
        config.callback_expr = null;
    },


    _on_remote_complete: function (rq, status) {
        Fenton._dequeue_request();
    },


    _log_remote: function (msg) {
        $.ajax({
            dataType: 'json',
            traditional: true,
            dataFilter: null,
            error: null,
            complete: null,
            success: null,
            data: $.param([
                {name: Fenton.config.xmlhttp_key, value: 1},
                {name: Fenton.config.bind_key, value: Fenton.config.logger_bind},
                {name: 'msg', value: msg}
            ]),
            context: {target: this}
        });
    },


    getSelectionStart: function(el) {
        if (!el.createTextRange)
            return el.selectionStart;
        var r = document.selection.createRange().duplicate();
        r.moveEnd('character', el.value.length);
        if (r.text == '')
            return el.value.length;
        return el.value.lastIndexOf(r.text);
    },


    getSelectionEnd: function(el) {
        if (!el.createTextRange)
            return el.selectionEnd;
        var r = document.selection.createRange().duplicate();
        r.moveStart('character', -el.value.length);
        return r.text.lengt;
    }

});



Fenton.widgets.base_widget = {
    change_on_dirty: true,
    init: function () {
        return this;
    },

    bind: function() {
        this.after_bind && this.after_bind();
    },

    el: function () {
        return $('#' + this.id);
    },

    activate_remote: function (id, data) {
        if (Fenton._requests_disabled)
            return;
        if (data) {
            var encoded = [];
            for (var k in data)
                encoded.push({name:k,
                              value:data[k]});
            data = encoded;
        }
        else
            data = $(Fenton._form).serializeArray();

        if (this.field)
            data.push({name:'____trigger_field', value: this.field});

        data.push({name: Fenton.config.xmlhttp_key, value: 1});
        data.push({name: Fenton.config.bind_key, value: id});
        var args = {
            data: $.param(data),
            context: {target: this}
        };
        this.ack_request && this.ack_request.call(this, args.context);
        Fenton._enqueue_request(args);
        Fenton._dequeue_request();
        return args.context;
    },

    ondirty: function (e) {
        $(Fenton.screen).trigger('dirty', [this]);
    },

    onchange: function (e, refresh) {
        $(Fenton.screen).trigger('change', [this, refresh || this.change_refreshes]);
    }
};


Fenton.define_widgets([{
    widget_class: 'screen_frame',
    init: function () {
        Fenton.screen = this;
        return this;
    },

    bind: function () {
        if (this.heartbeat_id && this.heartbeat_seconds) {
            this.heartbeat_pulse = this.heartbeat_pulse.bind(this);
            this._heartbeat_send = this._heartbeat_send.bind(this);
            this._heartbeat_dead = this._heartbeat_dead.bind(this);
            this._heartbeat_error = this._heartbeat_error.bind(this);
            Fenton.callbacks.heartbeat_pulse = this.heartbeat_pulse;
            this.heartbeat_pulse();
        }

        var msg = Fenton.config.message;
        if (msg)
            Fenton.config.form_error = Fenton.show_message(msg[0], msg[1]);

    },

    ondirty: function (e, target) {
        window.clearTimeout(this.dirty_timeout);
        if (target.change_on_dirty)
            this.dirty_timeout = window.setTimeout(this._changer(target),
                                                   this.update_delay || 2000);
    },

    onchange: function (e, target, refresh) {
        window.clearTimeout(this.dirty_timeout);
        if (this.update_bind)
            target.activate_remote(this.update_bind).refresh = refresh
    },

    _changer: function (target) {
        return function () {
            $(target).trigger('change');
        };
    },

    _heartbeat_error: function (msg, f) {
        //Fenton._disable_all();
        var ticker = $('<span>60 s</span>');
        Fenton.show_error($('<span>' + msg + '; refreshing in </span>')
                         .append(ticker));
        Fenton._log_remote(msg);
        (function (i) {
            i = parseInt(ticker.text());
            if (i <= 1)
                return f();
            ticker.text((i-1) + ' s');
            window.setTimeout(arguments.callee, 1000);
        })();
    },

    _heartbeat_dead: function () {
        Fenton._disable_all();
        this._heartbeat_error('Lost connection to server', Fenton.refresh_screen);
    },

    _heartbeat_send: function () {
        window.clearTimeout(this._pulse_timeout);
        window.clearTimeout(this._dead_timeout);
        if (this._expires && (new Date) > this._expires)
            return this._heartbeat_error('Page expired',
            function () {
                window.history.go(-1)
            });

        this._dead_timeout = window.setTimeout(this._heartbeat_dead,
                                               this.heartbeat_seconds * 10000);
        this.activate_remote(this.heartbeat_id);
    },

    heartbeat_pulse: function () {
        if (!this.heartbeat_id)
            return;
        window.clearTimeout(this._pulse_timeout);
        window.clearTimeout(this._dead_timeout);
        var now = new Date;
        //$.log.debug('now: ' + now);
        this._expires = this.expire_seconds
                      ? (now*1 + this.expire_seconds*1000)
                      : null;
        //$.log.debug('nxt: ' + (new Date(now*1 + this.heartbeat_seconds*1000)));
        //if (this._expires)
        //    $.log.debug('exp: ' + (new Date(this._expires)));
        this._pulse_timeout = window.setTimeout(this._heartbeat_send,
                                                this.heartbeat_seconds * 1000);
    }

}, {

    widget_class: 'sheet_body',
    after_bind: function () {

        var el = this.el(),
        child = $('#'+this.id + ' > div.content'),
        t = null,
        reflow = function() {
            $.log.debug('reflow');
            el.css({
                width: $(window).width() - el.offset().left,
                height: $(window).height() - el.offset().top,
                overflow: 'auto'
            });

            child.css({
                'float': 'left',
                width: 'auto',
                margin: 0
            });
            child.css({
                'float': 'none',
                width: child.width() + 'px',
                margin: 'auto'
            });
            child.show();
        };

        setTimeout(reflow, 10);

        $(window).resize(function(){
            clearTimeout(t);
            t = setTimeout(reflow, 100);
        });
    }

}, {

    widget_class: 'submit_button',
    after_bind: function () {
        this.el().click(this.onclick);
        this.el().dblclick(function(){
            this.disabled=false;
            alert('No! Don\'t double-click!')
            this.disabled=false;
            return false;
        });
    },
    onclick: function () {
        if (this.disabled)
            return;
        this.disabled = true;
        $('#'+Fenton.config.bind_key).val(this.id);
        this.form.submit();
    }

}, {

    widget_class: 'toolbar_button',
    disabled: false,
    after_bind: function () {
        this.enable = this.enable.bind(this);
        this.disable = this.disable.bind(this);
        this.activate = this.activate.bind(this);

        this.el().click(this.activate);

        if (this.dirty_disables) {
            $(Fenton.screen).bind('dirty', this.disable);
            $(Fenton.screen).bind('clean', this.enable);
        }

        if (this.el().attr('disabled') && !this.disabled)
            window.setTimeout(this.enable, 10);
    },
    activate: function () {
        this.disable();
        if (!this.confirm || confirm(this.confirm))
            this.activate_remote(this.el().attr('bind'));
        else
            this.enable();
        return false;
    },
    disable: function () {
        this.el().attr('disabled', 'disabled');
    },
    enable: function () {
        this.el().removeAttr('disabled');
    }

}, {

    widget_class: 'toggle_button',
    after_bind: function () {
        $('#' + this.id + '_button')
            .click({widget:this}, this.clicked)
    },
    clicked: function (e) {
        var $this = $(this),
            self = e.data.widget,
            hidden = $('#' + self.id + '_hidden');
        this.disabled = true;
        hidden.val(hidden.val()=='True'?'False':'True');
        $(self).trigger('change');
        return false;
    }

}, {

    widget_class: 'model_form',

    init: function () {
        Fenton.model_form = this;
        this.current_focus = null;
        this.hint_visible = null;
        this.field_widgets = {};
        return this;
    },

    after_bind: function () {
        var self = this,
            el = this.el();
        $('tr.form-field', el).mousedown(function () {
            self.focus_field(this);
        });

        $('tr.form-field', el).each(function () {
            var _field = this;
            $('td.form-field-hint div.icon', this).click(function () {
                if (self.hint_visible == _field)
                    self.hide_hint(_field);
                else
                    self.show_hint(_field);
            });
        });
    },

    set_value: function (k, v) {
        if (this.field_widgets[k])
            this.field_widgets[k].set_value(v);
        else
            $.log.debug('No widget for field: ' + k);
    },

    set_error: function (k, e) {
        if (e)
            $('#' + k + '_field').addClass('has-alert');
        else
            $('#' + k + '_field').removeClass('has-alert');
        $('#' + k + '_error').html(e || '');
    },

    focus_field: function (f) {
        if (typeof(f) == 'string')
            f = $('#'+f+'_field').get(0);
        if (this.current_focus && this.current_focus == f)
            return;
        if (this.current_focus)
            this.unfocus_field(this.current_focus);
        $(f).addClass('focused');
        this.current_focus = f;
    },

    unfocus_field: function (f) {
        if (typeof(f) == 'string')
            f = $('#'+f).get(0);
        if (this.current_focus && this.current_focus != f)
            return;
        $(f).removeClass('focused');
        this.hide_hint(f);
        this.current_focus = null;
    },

    show_hint: function (f) {
        var self = this,
            pos = $(f).offset();
        if (!this.tip)
            this.tip = $('<div id=tooltip-popup/>').appendTo(document.body);
        this.tip.css({
            position: 'absolute',
            left: pos.left + 'px',
            top: (pos.top - 6) + 'px'
        })
        .html($('td.form-field-hint div.content', f).html())
        .show()
        .click(function () {
            self.hide_hint(f);
        });
        this.hint_visible = f;
    },

    hide_hint: function (f) {
        if (!this.tip)
            return;
        this.tip.hide();
        this.hint_visible = null;
    },

    hide_field: function () {
        for (var i=0; i<arguments.length; ++i)
            $('#'+arguments[i]+'_field').hide();
    },

    show_field: function() {
        for (var i=0; i<arguments.length; ++i)
            $('#'+arguments[i]+'_field').show();
    },

    toggle_field: function () {
        for (var i=0; i<arguments.length; ++i)
            $('#'+arguments[i]+'_field').toggle();
    }

}, {

    widget_class: 'field_widget',
    change_refreshes: false,
    bind: function() {
        if (Fenton.model_form)
            Fenton.model_form.field_widgets[this.field] = this;
        Fenton.widgets.base_widget.bind.call(this);
    },
    get_value: function () {
        return this.el().val();
    },
    set_value: function () {
    },
    onfocus: function () {
        Fenton.model_form.focus_field(this.field);
    },
    onblur: function () {
        Fenton.model_form.unfocus_field(this.field);
    }


}, {

    widget_class: 'string_view',
    extend: 'field_widget',
    set_value: function (value) {
        this.el().html(value);
    }

}, {

    widget_class: 'reference_view',
    extend: 'string_view',
    set_value: function (value) {
        this.el().html(value);
    }

}, {

    widget_class: 'string_edit',
    extend: 'field_widget',
    keypress_triggers_dirty: true,
    elfocused: function () {
        this.beforeval = this.el().val();
        $(this).trigger('focus');
    },
    elblurred: function () {
        $(this).trigger('blur');
    },
    elchanged: function () {
        $(this).trigger('change');
    },
    keypressed: function(el, e){
        switch (e.keyCode) {
        case Fenton.KEY_RETURN:
            if (el.tagName != 'TEXTAREA')
                return false;
        case Fenton.KEY_TAB:
            return;
        case Fenton.KEY_ESCAPE:
            el.value = this.beforeval;
            this.beforeval = null;
            el.blur && el.blur();
            return;
        }
        $(this).trigger('dirty');
    },
    after_bind: function () {
        var self = this;
        this.el()
            .focus(this.elfocused.bind(this))
            .blur(this.elblurred.bind(this))
            .change(this.elchanged.bind(this));

        if (this.keypress_triggers_dirty)
            this.el()[0].onkeydown=function(e){self.keypressed.call(self, this, e||event)};
    },
    set_value: function (value) {
        this.el().val(value);
    }

}, {

    widget_class: 'textbox_edit',
    extend: 'string_edit'

}, {

    widget_class: 'code_edit',
    extend: 'string_edit',
    after_bind: function() {
        this.regexp = new RegExp('[^' + this.chars + ']', 'g');
        var self = this;
        this.el()[0].onkeyup = function(e) {
            if (self['case'] == 'upper')
                this.value = this.value.toUpperCase();
            else if (self['case'] == 'lower')
                this.value = this.value.toLowerCase();
            this.value = this.value.replace(self.regexp, '');
        };
        Fenton.widgets.string_edit.after_bind.call(this);
    }

}, {

    widget_class: 'composite_control',
    extend: 'string_edit',
    get_value: function () {
        return this.get_hidden().val();
    },
    get_hidden: function () {
        return $('#' + this.id + '_value');
    }

}, {

    widget_class: 'date_edit',
    extend: 'composite_control',
    el: function () {
        return $('#' + this.id + '_date_input');
    },
    after_bind: function () {
        $('#' + this.id + '_date_input').date_input({
            getValue: this._get_date.bind(this),
            setValue: this._set_date.bind(this)
        });
        Fenton.widgets.composite_control.after_bind.call(this);
    },
    _get_date: function () {
        return Fenton.from_iso_date(this.get_hidden().val());
    },
    _set_date: function (v) {
        this.get_hidden().val(v ? Fenton.format_iso_date(v) : '');
        $(this).trigger('change');
    },
    set_value: function (value) {
        this.get_hidden().val(value);
        var dic = this.el().data('date_input_control');
        if (value)
            value = dic.formatDate(Fenton.from_iso_date(value));
        this.el().val(value || '');
    }

}, {

    widget_class: 'time_edit',
    extend: 'string_edit',
    onchange: function (e) {
        var orig = $.trim(this.el().val()),
            fixed = orig && Fenton.parse_time(orig);
        if (fixed || !orig) {
            this.el().val(fixed);
            Fenton.model_form.set_error(this.field, null);
            $(Fenton.screen).trigger('change', [this]);
        }
        else if (orig)
            Fenton.model_form.set_error(this.field, 'Invalid time');
    }

}, {

    widget_class: 'minutes_edit',
    extend: 'string_edit',
    onchange: function (e) {
        var orig = $.trim(this.el().val()),
            fixed = orig && Fenton.parse_minutes(orig);
        if (fixed || !orig) {
            this.el().val(fixed);
            Fenton.model_form.set_error(this.field, null);
            $(Fenton.screen).trigger('change', [this]);
        }
        else if (orig)
            Fenton.model_form.set_error(this.field, 'Value not in format (12h)(34(m))')
    }

}, {

    widget_class: 'local_datetime_edit',
    extend: 'field_widget',
    el: function () {
        return $('#' + this.id + '_date_input')
                .add('#' + this.id + '_time_input');
    },

    get_hidden: function () {
        return $('#' + this.id + '_value');
    },

    after_bind: function () {
        var self = this;

        self.el().focus(function () {
            self.focused = this;
            $(self).trigger('focus');
        })
        .blur(function () {
            if (self.focused == this)
                self.focused = null;
        });

        $('#' + self.id + '_date_input').date_input({
            getValue: function () {
                var di = $('#' + self.id + '_date_input'),
                    dic = di.data('date_input_control'),
                    d = self.get_default();
                di.val(dic.formatDate(d));
                return d;
            },
            setValue: function (v) {
                if (!v)
                    $('#' + self.id + '_time_input').val('');
                $(self).trigger('change');
            }
        });

        if (self.keypress_triggers_dirty) {
            self.el().keypress(function (e) {
                switch (e.keyCode) {
                case Fenton.KEY_RETURN:
                case Fenton.KEY_TAB:
                case Fenton.KEY_ESCAPE:
                    return;
                }
                $(self).trigger('dirty');
            });
        }

        $(self).focus(function () {
            if (!self.get_value())
                self.set_value(self.get_default());
        });

        $(self).blur(function () {
            self.focused = null;
            if (!self.get_hidden().val()) {
                $('#' + self.id + '_date_input').val('')
                $('#' + self.id + '_time_input').val('')
                self.date_value = null;
            }
        });

    },

    set_hidden: function (v) {
        this.get_hidden().val(v);
    },

    set_value: function (value) {
        if (value && value instanceof Date)
            value = Fenton.format_iso_timestamp(value);
        else if (!value)
            value = '';
        this.set_hidden(value ? value + Fenton.format_tz(this.tzoffset) : '');
        $('#' + this.id + '_time_input').val(value.substr(11, 5));
        value = value
                ? $('#' + this.id + '_date_input')
                    .data('date_input_control').formatDate(Fenton.from_iso_date(value))
                : '';
        $('#' + this.id + '_date_input').val(value);
    },

    get_default: function () {
        if (this.follows_from)
            return Fenton.model_form.fields[this.follows_from].get_value() || new Date;
        return new Date;
    },

    get_value: function () {
        return Fenton.from_iso_date(this.get_hidden().val());
    }

}, {

    widget_class: 'string_mask_edit',
    extend: 'string_edit'

}, {

    widget_class: 'child_list_edit',
    extend: 'field_widget',
    confirmText: 'Remove this item?',
    after_bind: function () {
        $('#'+this.id).click({widget: this}, this.clicked);
    },
    clicked: function (e) {
        var $this = $(e.target),
            bind = $this.attr('bind');
        if (!bind)
            return false;
        var self = e.data.widget;
        if ($this.hasClass('confirm-action') && confirm(self.confirmText)
                || $this.hasClass('immediate-action'))
            self.activate_remote(bind);
        return false;
    }

}, {

    widget_class: 'child_single_edit',
    extend: 'child_list_edit'

}, {

    widget_class: 'single_option_select',
    extend: 'string_edit',
    after_bind: function () {
        var self = this;
        this.el().focus(function () {
            $(self).trigger('focus');
        })
        .blur(function () {
            $(self).trigger('blur');
        })
        .change(function () {
            self.get_null_option().html(self.null_option_value);
            $(self).trigger('change');
        });
        $('#'+this.id+'_select_other').click(function () {
            self.el().val(self.other_key);
            $(self).trigger('change');
        });
    },
    get_null_option: function() {
        var e = this.el()[0];
        for (var i=0; i<e.options.length; ++i)
            if (!e.options[i].value)
                return $(e.options[i]);
    }

}, {

    widget_class: 'radio_select',
    extend: 'single_option_select',
    after_bind: function () {
        var self = this;
        $(':radio', this.el())
            .focus(function () {
                $(self).trigger('focus');
            })
            .blur(function () {
                $(self).trigger('blur');
            })
            .click(function () {
                $(self).trigger('change');
            });
    },
    set_value: function (value) {
        $(':radio', this.el()).each(function() {
            this.checked=(this.value == value);
        });
    }

}, {

    widget_class: 'simple_select_list',
    extend: 'string_edit',
    after_bind: function () {
        var data = {widget:this},
            el = this.el()
                .focus(data, this.focused)
                .blur(data, this.blurred);

        $(':checkbox', el)
            .focus(data, this.focused)
            .blur(data, this.blurred)
            .click(data, this.checked)
            .each(function () {
                if (this.checked)
                    $(this.parentNode).addClass('selected');
            });
    },
    focused: function (e) {
        $(e.data.widget).trigger('focus');
    },
    blurred: function (e) {
        $(e.data.widget).trigger('blur');
    },
    checked: function (e) {
        var $t = $(this);
        $t.closest('label').toggleClass('selected', $t.attr('checked'));
        $(e.data.widget).trigger('change');
    }

}]);






